#include "st_lcd_spi_drv.h"

#if LCD_GENERIC_SPI
#include <string.h>
#include "../common/spi_arch.h"
#include "../common/dma.h"
#include "lvgl/src/lv_core/lv_refr.h"
#include "lvgl/src/lv_core/lv_disp.h"
#include "../../board.h"

#define DC_CMD                          0
#define DC_DATA                         1

typedef struct {
  uint8_t *src;
  uint8_t *src_cur;
  int byte;
  int byte_cur;
  int remain;
  xfer_mode_t mode;
  bool cont;
  lv_disp_t *lv_disp;
} fb_xfer_info_t;

static struct lcd_spi_info *g_lcd_spi_info;
static fb_xfer_info_t fb_xfer_info;

static uint8_t spi_tx_buf[SPI_TX_SIZE + SPI_TX_DCX_BIT] __attribute__((aligned(32)));

static int
build_tx_buf(int dc, uint8_t *dst, uint8_t *src, int dst_bytes, int src_bytes)
{
  int offset, i, j, shift;
  uint8_t mask;

  offset = 7;
  j = 0;
  dc &= 0x1;
  memset(dst, 0x0, dst_bytes);
  for(i = 0; i < src_bytes; i++) {
    dst[j] |= (dc << offset);
    if(offset == 0) {
      j++;
      dst[j] = src[i];
      j++;
    } else {
      shift = 8 - offset;
      mask = (1 << offset) - 1;
      dst[j] |= (src[i] >> shift) & mask;
      j++;
      shift = offset;
      mask = ~((1 << offset) - 1);
      dst[j] |= (src[i] << shift) & mask;
    }
    offset = (offset == 0) ? 7 : (offset - 1);
  }

  return (src_bytes * 9);
}
static int
spi_write_3_line(int dc, uint8_t *buf,
                 int bits, bool cont, xfer_mode_t mode)
{
  int bits_tx;

  spi_set_dir(g_lcd_spi_info->dev, SPI_DIR_TX);
  bits_tx = build_tx_buf(dc, spi_tx_buf, buf, sizeof(spi_tx_buf), bits / 8);
  if(mode == XFER_MODE_DMA) {
      flush_cache((unsigned long)spi_tx_buf, bits_tx/8 + (bits_tx%8?1:0));
  }

  return spi_transfer_bits(g_lcd_spi_info->dev, cont, spi_tx_buf, NULL, bits_tx, mode);
}
static int
spi_read_3_line(uint8_t *buf, int bits, bool cont, xfer_mode_t mode)
{
  int ret;

  spi_set_dir(g_lcd_spi_info->dev, SPI_DIR_RX);
  if(bits > 8) {
    ret = spi_transfer_bits(g_lcd_spi_info->dev, 1, NULL, buf, 1, mode);     /* 1 dummy clock cycle */
    if(ret < 0) {
      return ret;
    }
  }
  return spi_transfer_bits(g_lcd_spi_info->dev, cont, NULL, buf, bits, mode);
}
static int
spi_read_4_line(uint8_t *buf,
                int bits, bool cont, xfer_mode_t mode)
{
  int ret;

  spi_set_dir(g_lcd_spi_info->dev, SPI_DIR_RX);
  if(bits > 8) {
    ret = spi_transfer_bits(g_lcd_spi_info->dev, 1, NULL, buf, 1, mode);     /* 1 dummy clock cycle */
    if(ret < 0) {
      return ret;
    }
  }
  return spi_transfer_bits(g_lcd_spi_info->dev, cont, NULL, buf, bits, mode);
}
static int
spi_write_4_line(int dc, uint8_t *buf, int bits, bool cont, xfer_mode_t mode)
{
  if(g_lcd_spi_info == NULL) {
    return -1;
  }
  /*4 line mode, set DCX pin first*/
  gpio_set_direction(g_lcd_spi_info->dc_pin, GPIO_OUT);
  gpio_output_set(g_lcd_spi_info->dc_pin, dc);

  spi_set_dir(g_lcd_spi_info->dev, SPI_DIR_TX);

  if(mode == XFER_MODE_DMA) {
      flush_cache((unsigned long)buf, bits/8 + (bits%8?1:0));
  }

  return spi_transfer_bits(g_lcd_spi_info->dev, cont, buf, NULL, bits, mode);
}
int
lcd_spi_init(struct lcd_spi_info *info)
{
  printf("lcd_spi_init, line_num = %d\n", info->protocol == INTERFACE_PROTOCOL_SPI_3_LINE?3:4);
  if(info == NULL) {
    cplog_printf("lcd_spi_init, invalid param!\n");
    return -1;
  }

  spi_param_t arg;
  arg.bus = info->dev;
  arg.clk = info->wclk;
  arg.spi_mode = info->spi_mode;
  arg.wire_mode = SPI_3_WIRE_MODE;
  arg.cs_pin = info->cs_pin;
  arg.mfp_cfg = info->mfp;

  if(spi_arch_init(&arg) < 0) {
    cplog_printf("lcd_spi_init failed!!!!\n");
    return -1;
  }

  g_lcd_spi_info = info;
  if(info->protocol == INTERFACE_PROTOCOL_SPI_3_LINE) {
    g_lcd_spi_info->spi_read = spi_read_3_line;
    g_lcd_spi_info->spi_write = spi_write_3_line;
  } else {
    g_lcd_spi_info->spi_read = spi_read_4_line;
    g_lcd_spi_info->spi_write = spi_write_4_line;
  }

  return 0;
}
int
lcd_spi_write_cmd(uint32_t cmd, int bits)
{
  int ret = -1;
  if(g_lcd_spi_info) {
    spi_set_clk(g_lcd_spi_info->dev, g_lcd_spi_info->wclk);
    ret = g_lcd_spi_info->spi_write(DC_CMD, (uint8_t *)&cmd, bits, 0, XFER_MODE_POLL);
  }

  return ret;
}
int
lcd_spi_write_data(uint32_t data, int bits)
{
  int ret = -1;
  if(g_lcd_spi_info) {
    spi_set_clk(g_lcd_spi_info->dev, g_lcd_spi_info->wclk);
    ret = g_lcd_spi_info->spi_write(DC_DATA, (uint8_t *)&data, bits, 0, XFER_MODE_POLL);
  }

  return ret;
}
int
lcd_spi_read_data(uint32_t cmd, int cmd_bits, uint32_t *data, int data_bits, uint8_t read_gram)
{
  int ret = -1, cont;

  cont = 1;
  if(g_lcd_spi_info) {
    spi_set_clk(g_lcd_spi_info->dev, g_lcd_spi_info->rclk);
    ret = g_lcd_spi_info->spi_write(DC_CMD, (uint8_t *)&cmd, cmd_bits, cont, XFER_MODE_POLL);
    if(ret < 0) {
      return ret;
    }
    cont = 0;
    ret = g_lcd_spi_info->spi_read((uint8_t *)data, data_bits, cont, XFER_MODE_POLL);
  }

  return ret;
}
static void
fb_complete(void *arg)
{
  //printf("spi complete\n");
  fb_xfer_info_t *p = (fb_xfer_info_t *)arg;

  p->remain -= p->byte_cur;
  if(p->remain) {
    p->src_cur += p->byte_cur;
    if(g_lcd_spi_info->protocol == INTERFACE_PROTOCOL_SPI_3_LINE) {
      p->byte_cur = (p->remain < SPI_TX_SIZE) ? p->remain : SPI_TX_SIZE;
    } else {
      if(p->mode == XFER_MODE_DMA) {
        p->byte_cur = (p->remain < SPI_DMA_MAX_SIZE) ? p->remain : SPI_DMA_MAX_SIZE;
      } else {
        p->byte_cur = p->remain;
      }
    }
    g_lcd_spi_info->spi_write(DC_DATA, p->src_cur, p->byte_cur * 8, p->cont, p->mode);
  } else {
    spi_register_complete_cb(g_lcd_spi_info->dev, NULL, NULL);
    lv_disp_flush_ready(&(p->lv_disp->driver));
  }
}
int
lcd_spi_write_fb(uint8_t *buf, int byte, xfer_mode_t mode)
{
  int ret = 0;
#ifdef USE_WATCH_LITE
  uint32_t flag = 0;
  int status = 0;
  uint32_t timeout = 8;//lcd refresh rate 55HZ
#endif

  if(g_lcd_spi_info) {
    fb_xfer_info_t *p = &fb_xfer_info;
    spi_set_clk(g_lcd_spi_info->dev, g_lcd_spi_info->wclk);
    p->src = buf;
    p->src_cur = buf;
    p->byte = byte;
    p->byte_cur = 0;
    p->remain = byte;
    p->mode = mode;
    p->cont = false;
    if(mode != XFER_MODE_POLL) {
      spi_register_complete_cb(g_lcd_spi_info->dev, fb_complete, p);
      fb_complete(p);
    } else {
      while(p->remain) {
        fb_complete(p);
#ifdef USE_WATCH_LITE
        if (te_flag_ref != -1) {
            status = uos_wait_flag(te_flag_ref, LCD_TE_COME, UOS_FLAG_OR_CLEAR, &flag, timeout);
        }

        if (status) {
            printf("wait lcd te flag error\n");
        }
#endif
      }
    }
  }

  return ret;
}
int
lcd_spi_read8(uint8_t cmd, uint8_t *data)
{
  return lcd_spi_read_data(cmd, 8, (uint32_t *)data, 8, 0);
}
int
lcd_spi_read24(uint8_t cmd, uint32_t *data)
{
  uint8_t val[3];
  if(lcd_spi_read_data(cmd, 8, (uint32_t *)val, 24, 0) < 0) {
    return -1;
  }

  *data = (val[0] << 16) | (val[1] << 8) | val[2];
  return 0;
}
int
lcd_spi_read32(uint8_t cmd, uint32_t *data)
{
  uint8_t val[4];
  if(lcd_spi_read_data(cmd, 8, (uint32_t *)val, 32, 0) < 0) {
    return -1;
  }

  *data = (val[0] << 23) | (val[1] << 16) | (val[2] << 8) | val[3];
  return 0;
}
void
lcdc_set_disp(lv_disp_t *disp)
{
  fb_xfer_info.lv_disp = disp;
}
#endif
